{
  SQL脚本编辑窗口
  Create by Huz(huz0123@21cn.com) 2004/02/02
  Changed: 2005/04
  http://www.netsky-tech.com


}

unit uDMLSqlEditor;

{$MODE Delphi}

interface

uses
  LCLIntf, LCLType, LMessages, Messages, SysUtils, Variants, Classes,
  Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, Buttons, ActnList, StdActns, DB, DBCtrls,
  Grids, DBGrids, Menus, ComCtrls, ValEdit, CtMetaTable;

type

  { TfrmDmlSqlEditor }

  TfrmDmlSqlEditor = class(TForm)
    actCopyCol: TAction;
    actChangeValue: TAction;
    actCopyFieldEqVal: TAction;
    actDeleteRec: TAction;
    actCopyInsertRecSql: TAction;
    actCopyInsertSqlAll: TAction;
    actShowSqlTbProp: TAction;
    actShowContentPanel: TAction;
    combDbUsers: TComboBox;
    edtTbListFilter: TEdit;
    ImageList1: TImageList;
    lbStatus: TLabel;
    ListBoxTbs: TListBox;
    MemoFieldContent: TDBMemo;
    MenuItem1: TMenuItem;
    MenuItem10: TMenuItem;
    MenuItem11: TMenuItem;
    MenuItem14: TMenuItem;
    MenuItem2: TMenuItem;
    MenuItem3: TMenuItem;
    MenuItem4: TMenuItem;
    MenuItem5: TMenuItem;
    MenuItem6: TMenuItem;
    MenuItem7: TMenuItem;
    MenuItem8: TMenuItem;
    MenuItem9: TMenuItem;
    MN_QueryRelTable2: TMenuItem;
    MN_ShowSqlTbPropA: TMenuItem;
    MN_QueryRelTable: TMenuItem;
    MN_CopyTbName: TMenuItem;
    MN_QueryTableData: TMenuItem;
    MN_ShowSqlTbProp1: TMenuItem;
    MN_ShowTableProps: TMenuItem;
    MN_TbsRefresh: TMenuItem;
    N6: TMenuItem;
    PanelSql: TPanel;
    PanelRes: TPanel;
    Bevel3: TBevel;
    PanelTbs: TPanel;
    PopupMenuProp: TPopupMenu;
    PopupMenuTbs: TPopupMenu;
    sbtnToggleTbList: TSpeedButton;
    sbtnShowContPanel: TSpeedButton;
    Splitter1: TSplitter;
    ActionList1: TActionList;
    actEditSelall: TAction;
    DataSourceRes: TDataSource;
    DBGridRes: TDBGrid;
    Label1: TLabel;
    actExec: TAction;
    Bevel1: TBevel;
    Panel1: TPanel;
    Panel2: TPanel;
    PopupMenuOldSql: TPopupMenu;
    actCopyRec: TAction;
    PopupMenuGrid: TPopupMenu;
    N5: TMenuItem;
    actCopyAll: TAction;
    N1: TMenuItem;
    actRollback: TAction;
    actCommit: TAction;
    SplitterFV: TSplitter;
    SplitterTbs: TSplitter;
    TimerInit: TTimer;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    ToolButton_Commit: TToolButton;
    ToolButton_Exec: TToolButton;
    ToolButton_ClearAll: TToolButton;
    ToolButton_Connect: TToolButton;
    ToolButton_SqlNext: TToolButton;
    ToolButton_SqlPrev: TToolButton;
    ToolButton_SQL: TToolButton;
    ValueListEditorDS: TValueListEditor;
    procedure actChangeValueExecute(Sender: TObject);
    procedure actCopyColExecute(Sender: TObject);
    procedure actCopyFieldEqValExecute(Sender: TObject);
    procedure actCopyInsertRecSqlExecute(Sender: TObject);
    procedure actCopyInsertSqlAllExecute(Sender: TObject);
    procedure actDeleteRecExecute(Sender: TObject);
    procedure actShowContentPanelExecute(Sender: TObject);
    procedure actShowSqlTbPropExecute(Sender: TObject);
    procedure combDbUsersChange(Sender: TObject);
    procedure DBGridResCellClick(Column: TColumn);
    procedure DBGridResColEnter(Sender: TObject);
    procedure DBGridResEditingDone(Sender: TObject);
    procedure edtTbListFilterChange(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormHide(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure ListBoxTbsDblClick(Sender: TObject);
    procedure MN_CopyTbNameClick(Sender: TObject);
    procedure MN_QueryTableDataClick(Sender: TObject);
    procedure MN_ShowTablePropsClick(Sender: TObject);
    procedure MN_TbsRefreshClick(Sender: TObject);
    procedure sbtnToggleTbListClick(Sender: TObject);
    procedure Splitter1Moved(Sender: TObject);
    procedure actEditSelallExecute(Sender: TObject);
    procedure actExecExecute(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure PopupMenuOldSqlPopup(Sender: TObject);
    procedure actCopyRecExecute(Sender: TObject);
    procedure DBGridResMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: integer);
    procedure actCopyAllExecute(Sender: TObject);
    procedure PopupMenuGridPopup(Sender: TObject);
    procedure actExecUpdate(Sender: TObject);
    procedure TimerInitTimer(Sender: TObject);
    procedure ToolBar1Resize(Sender: TObject);
    procedure ToolButton_ClearAllClick(Sender: TObject);
    procedure ToolButton_CommitClick(Sender: TObject);
    procedure ToolButton_ConnectClick(Sender: TObject);
    procedure ToolButton_SQLMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ToolButton_SqlNextClick(Sender: TObject);
    procedure ToolButton_SqlPrevClick(Sender: TObject);
    procedure ValueListEditorDSEditingDone(Sender: TObject);
    procedure ValueListEditorDSSelectCell(Sender: TObject; aCol, aRow: Integer;
      var CanSelect: Boolean);
  private
    FAfterExec: TNotifyEvent;
    FBeforeExec: TNotifyEvent;
    { Private declarations }
    FSplitPercent: double;
    FNeedSaveIni: boolean;
    FOldSqlList: TStrings;
    FRecCount: integer;
    FHisIndex: integer;
    FSaveCurSql: string;
    FReadOnlyMode: boolean;
    FExecSqlModified: boolean;
    FOptionsStr: string;
    FLastTbListDBInfo: string;
    FLastTbListStr: string;

    procedure CheckLoadOldSql;
    procedure DoExecute;

    procedure CreateSqlMenus(MMenuItem: TMenuItem);
    procedure MItem_SqlClick(Sender: TObject);
    procedure SetHistorySql(Index: integer);

    procedure MN_QueryRelTb_Click(Sender: TObject);

    procedure LoadIniFile;
    procedure SaveIniFile;
    procedure SetReadOnlyMode(const Value: boolean);
    procedure SetSplitPercent(const Value: double);
    procedure RefreshTbList(bForce, bCheckMode: boolean);
    procedure DoDbTableAction(act: string);
    function GetCurRecInserSql(tbn, dbType: string): string;
    procedure GenDSValList;  
    procedure _OnDSAfterScroll(DataSet: TDataSet);
  public
    { Public declarations }
    FCtMetaDatabase: TCtMetaDatabase;
    ResultDataSet: TDataSet;
    MemoSql: TMemo;
    AutoExecSql: string;
    Proc_CheckSpecValue:
    function(const Value: string): string of object;
    procedure ClearSql;

    property ReadOnlyMode: boolean read FReadOnlyMode write SetReadOnlyMode;
    property OptionsStr: string read FOptionsStr write FOptionsStr;
    property SplitPercent: double read FSplitPercent write SetSplitPercent;
    property BeforeExec: TNotifyEvent read FBeforeExec write FBeforeExec;
    property AfterExec: TNotifyEvent read FAfterExec write FAfterExec;
  end;

function SReplaceCRX(const st: string): string;
function SReplaceCR(const st: string): string;

var
  FDmlSqlEditorForm: TfrmDmlSqlEditor;

implementation

uses
  dmlstrs, WindowFuncs, IniFiles, ClipBrd, uFormCtDbLogon, PvtInput, sqldb, dbconst;

{$R *.lfm}

type
  TDataSetXX=class(TDataSet)
  end;

function SReplaceCR(const st: string): string;
begin
  Result := StringReplace(st, '#13', #13, [rfReplaceAll]);
  Result := StringReplace(Result, '#10', #10, [rfReplaceAll]);
end;

function SReplaceCRX(const st: string): string;
begin
  Result := StringReplace(st, #13, '#13', [rfReplaceAll]);
  Result := StringReplace(Result, #10, '#10', [rfReplaceAll]);
end;

{ TfrmCTDebugSql }

procedure TfrmDmlSqlEditor.FormCreate(Sender: TObject);
begin
  MemoSql := TMemo.Create(Self);
  MemoSql.Align := alClient;
  MemoSql.Parent := Panel1;
  MemoSql.ScrollBars := ssBoth;

  ValueListEditorDS.TitleCaptions.Add(srDictKey);   
  ValueListEditorDS.TitleCaptions.Add(srDictValue);

  FSplitPercent := PanelRes.Height / Height * 100;
end;

procedure TfrmDmlSqlEditor.FormHide(Sender: TObject);
begin
  if Assigned(FCtMetaDatabase) then
    if FCtMetaDatabase.Connected then
      FCtMetaDatabase.ExecCmd('commit','','');
end;

procedure TfrmDmlSqlEditor.DBGridResCellClick(Column: TColumn);
begin
  if DBGridRes.SelectedField = nil then
    Exit;
  MemoFieldContent.DataField := DBGridRes.SelectedField.FieldName;
end;

procedure TfrmDmlSqlEditor.combDbUsersChange(Sender: TObject);
begin
  RefreshTbList(False, False);
end;

procedure TfrmDmlSqlEditor.actCopyColExecute(Sender: TObject);
var
  S, Ts: string;
  fd: TField;
begin
  fd := DBGridRes.SelectedField;
  if fd = nil then
    Exit;
  if ResultDataSet <> nil then
    if ResultDataSet.Active then
      with ResultDataSet do
      begin
        Ts := '';

        First;
        while not EOF do
        begin
          S := fd.AsString;
          if Ts <> '' then
            Ts := Ts + #13#10;
          Ts := Ts + S;
          Next;
        end;
        if Ts <> '' then
          Clipboard.AsText := Ts;
      end;
end;

procedure TfrmDmlSqlEditor.actCopyFieldEqValExecute(Sender: TObject);
var
  V, S, eg: string;
  fd: TField;
begin
  fd := DBGridRes.SelectedField;
  if fd = nil then
    Exit;
  V:=fd.AsString;
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;
  eg := '';
  if Assigned(FCtMetaDatabase) then
    eg := FCtMetaDatabase.EngineType;
  case fd.DataType of
    ftDate,  ftTime, ftDateTime, ftTimeStamp:
    begin
      V := DBSqlStringToDateTime(V, eg);
    end;
    ftSmallint, ftInteger, ftWord, ftFloat, ftCurrency, ftBCD,
    ftAutoInc, ftLargeint, ftFMTBcd:
    begin
    end;
  else
    V := GetDbQuotString(V, eg);
  end;        
  if Trim(V) = '' then
    V := 'null';
  S := fd.FieldName+' = '+V;   
  Clipboard.AsText := S;
end;

procedure TfrmDmlSqlEditor.actCopyInsertRecSqlExecute(Sender: TObject);
var
  tbn, S, eg: string;
begin            
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;

  tbn := actShowSqlTbProp.Hint;
  if tbn = '' then
    tbn := 'TableName';
  eg := '';
  if Assigned(FCtMetaDatabase) then
    eg := FCtMetaDatabase.EngineType;

  if not PvtChoiceQuery(actCopyInsertRecSql.Caption, srChooseDbType, eg,
    'ORACLE' + #13#10 +
    'MYSQL' + #13#10 +
    'SQLSERVER' + #13#10 +
    'POSTGRESQL' + #13#10 +
    'SQLITE' + #13#10 +
    'STANDARD' + #13#10 +
    '', '') then
    Exit;

  S := GetCurRecInserSql(tbn, eg);
  Clipboard.AsText := S;
end;

procedure TfrmDmlSqlEditor.actCopyInsertSqlAllExecute(Sender: TObject);
var
  tbn, S, eg: string;
  ts: TStringList;
begin
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;

  tbn := actShowSqlTbProp.Hint;
  if tbn = '' then
    tbn := 'TableName';
  eg := '';
  if Assigned(FCtMetaDatabase) then
    eg := FCtMetaDatabase.EngineType;

  if not PvtChoiceQuery(actCopyInsertSqlAll.Caption, srChooseDbType, eg,
    'ORACLE' + #13#10 +
    'MYSQL' + #13#10 +
    'SQLSERVER' + #13#10 +
    'POSTGRESQL' + #13#10 +
    'SQLITE' + #13#10 +
    'STANDARD' + #13#10 +
    '', '') then
    Exit;

  ResultDataSet.First;   
  ts:= TStringList.Create;
  try
    while not ResultDataSet.EOF do
    begin
      S := GetCurRecInserSql(tbn, eg);
      ts.Add(S);       
      ts.Add('');
      ResultDataSet.Next;
    end;
    Clipboard.AsText := ts.Text;
  finally
    ts.Free;
  end;
end;

procedure TfrmDmlSqlEditor.actDeleteRecExecute(Sender: TObject);
begin
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;    
  if ResultDataSet.Eof then
    Exit;          
  if Application.MessageBox(PChar(srConfirmDeleteCurRec),
    PChar(srCapDelete), MB_OKCANCEL or MB_ICONWARNING) <> IDOK then
    Exit;
  ResultDataSet.Delete;
end;

procedure TfrmDmlSqlEditor.actShowContentPanelExecute(Sender: TObject);
begin
  if ValueListEditorDS.Visible then
  begin
    MemoFieldContent.Show;
    ValueListEditorDS.Hide;
    SplitterFV.Left := MemoFieldContent.Left - 4;
    SplitterFV.Show;
  end
  else if MemoFieldContent.Visible then
  begin      
    SplitterFV.Visible:=False;
    MemoFieldContent.Visible :=False; 
    ValueListEditorDS.Visible :=False;
  end
  else
  begin
    GenDSValList;
    ValueListEditorDS.Show;
    MemoFieldContent.Visible :=False;
    SplitterFV.Left := ValueListEditorDS.Left - 4;
    SplitterFV.Show;
  end;
end;

procedure TfrmDmlSqlEditor.actShowSqlTbPropExecute(Sender: TObject);
var
  atb: TCtMetaTable;
  db, tbn: string;
  o: TCtMetaObject;
begin
  tbn := actShowSqlTbProp.Hint;
  if tbn = '' then
    Exit;
  atb := FGlobeDataModelList.GetTableOfName(tbn);
  if atb<>nil then
  begin
    if not Assigned(Proc_ShowCtTableOfField) then
      raise Exception.Create('Proc_ShowCtTableOfField not assigned');
    Proc_ShowCtTableOfField(atb, nil, True, False, True);
  end
  else
  begin
    db := combDBUsers.Text;
    Screen.Cursor := crAppStart;
    try
      o := FCtMetaDatabase.GetObjInfos(db, tbn, '');
    finally
      Screen.Cursor := crDefault;
    end;

    if o = nil then
      raise Exception.Create(srDmlSearchNotFound + ' - ' + db+IfElse(db<>'','.','')+tbn);
    try
      if not Assigned(Proc_ShowCtTableOfField) then
        raise Exception.Create('Proc_ShowCtTableOfField not assigned');
      Proc_ShowCtTableOfField(TCtMetaTable(o), nil, True, False, True);
    finally
      o.Free;
    end;
  end;
end;

procedure TfrmDmlSqlEditor.actChangeValueExecute(Sender: TObject);
var
  V: string;
  fd: TField;
begin
  fd := DBGridRes.SelectedField;
  if fd = nil then
    Exit;
  V:=fd.AsString;
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;
  if PvtInput.PvtMemoQuery(actChangeValue.Caption, fd.FieldName, V, fd.ReadOnly) then
    if not fd.ReadOnly then
    begin
      ResultDataSet.Edit;
      fd.AsString:=V;   
      if ValueListEditorDS.Visible then
        GenDSValList;
    end;
end;

procedure TfrmDmlSqlEditor.DBGridResColEnter(Sender: TObject);
begin
  DBGridResCellClick(nil);
end;

procedure TfrmDmlSqlEditor.DBGridResEditingDone(Sender: TObject);
begin
  if ValueListEditorDS.Visible then
    GenDSValList;
end;

procedure TfrmDmlSqlEditor.edtTbListFilterChange(Sender: TObject);
var
  S, F: String;
  I: Integer;
begin
  if FLastTbListStr = '' then
    Exit;
  F := Trim(LowerCase(edtTbListFilter.Text));

  ListBoxTbs.Items.BeginUpdate;
  try
    ListBoxTbs.Items.Text := FLastTbListStr;
    if F='' then
      Exit;
    for I:=ListBoxTbs.Items.Count - 1 downto 0 do
    begin
      S := LowerCase(ListBoxTbs.Items[I]);
      if Pos(F, S)=0 then
        ListBoxTbs.Items.Delete(I);
    end;
  finally
    ListBoxTbs.Items.EndUpdate;
  end;
end;

procedure TfrmDmlSqlEditor.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  CloseAction := caFree;
end;

procedure TfrmDmlSqlEditor.FormResize(Sender: TObject);
begin
  if PanelRes.Visible and (FSplitPercent <> 0) then
    PanelRes.Height := Round(FSplitPercent * Height / 100);
  DBGridRes.Realign;
end;

procedure TfrmDmlSqlEditor.FormShow(Sender: TObject);
begin
  CtSetFixWidthFont(MemoSql);
end;


procedure TfrmDmlSqlEditor.ListBoxTbsDblClick(Sender: TObject);
begin
  DoDbTableAction('show_props');
end;


procedure TfrmDmlSqlEditor.MN_CopyTbNameClick(Sender: TObject);
begin
  if ListBoxTbs.ItemIndex < 0 then
    Exit;
  ClipBoard.AsText := ListBoxTbs.Items[ListBoxTbs.ItemIndex];
end;

procedure TfrmDmlSqlEditor.MN_QueryTableDataClick(Sender: TObject);
begin
  DoDbTableAction('get_select_sql');
end;

procedure TfrmDmlSqlEditor.MN_ShowTablePropsClick(Sender: TObject);
begin
  DoDbTableAction('show_props');
end;

procedure TfrmDmlSqlEditor.MN_TbsRefreshClick(Sender: TObject);
begin
  RefreshTbList(True, False);
end;


procedure TfrmDmlSqlEditor.sbtnToggleTbListClick(Sender: TObject);
begin
  if PanelTbs.Visible then
  begin
    Self.SplitterTbs.Visible := False;
    PanelTbs.Visible := False;
  end
  else
  begin
    PanelTbs.Visible := True;
    SplitterTbs.Left := PanelTbs.Left - SplitterTbs.Width;
    SplitterTbs.Visible := True;
    RefreshTbList(True, False);
  end;
end;

procedure TfrmDmlSqlEditor.Splitter1Moved(Sender: TObject);
begin
  FSplitPercent := PanelRes.Height / Height * 100;
end;

procedure TfrmDmlSqlEditor.TimerInitTimer(Sender: TObject);
begin
  TimerInit.Enabled := False;
  ToolBar1Resize(nil);
  if not Assigned(FCtMetaDatabase) then
  begin
    if CanAutoShowLogin or (GetLastCtDbConn >= 0) then
      ToolButton_ConnectClick(nil);
    if Assigned(FCtMetaDatabase) and FCtMetaDatabase.Connected then
      if AutoExecSql <> '' then
      begin
        MemoSql.Lines.Text := AutoExecSql;
        actExec.Execute;
      end;
  end;
end;

procedure TfrmDmlSqlEditor.ToolBar1Resize(Sender: TObject);
begin
  lbStatus.Left := ToolBar1.Left + ToolBar1.Width + ScaleDpiSize(6);
  lbStatus.Top := ToolBar1.Top + (ToolBar1.Height - lbStatus.Height) div 2;
end;

procedure TfrmDmlSqlEditor.ToolButton_ClearAllClick(Sender: TObject);
var
  sql: string;
begin
  sql := Trim(MemoSql.Lines.Text);
  if (sql <> '') and (sql <> Trim(AutoExecSql)) and (Trim(AutoExecSql) <> '') then
  begin
    MemoSql.Lines.Text := AutoExecSql;
  end
  else
  begin
    MemoSql.Lines.Text := '';
    AutoExecSql := '';
  end;
  FExecSqlModified := False;
  if ResultDataSet <> nil then
    if ResultDataSet.Active then
      ResultDataSet.Close;
end;

procedure TfrmDmlSqlEditor.ToolButton_CommitClick(Sender: TObject);
begin
  if Assigned(FCtMetaDatabase) then
    if FCtMetaDatabase.Connected then
    begin
      FCtMetaDatabase.ExecCmd('commit','','');
      lbStatus.Caption := 'commited: '+TimeToStr(Now);
    end;
end;

procedure TfrmDmlSqlEditor.ToolButton_ConnectClick(Sender: TObject);
var
  I: integer;
begin
  I := GetLastCtDbConn;
  if (Sender <> nil) or (I < 0) then
  begin         
    lbStatus.Caption := 'Connecting...';
    I := ExecCtDbLogon;
  end;
  if I >= 0 then
  begin
    FCtMetaDatabase := CtMetaDBRegs[I].DbImpl;
    lbStatus.Caption := 'Connected to [' + FCtMetaDatabase.EngineType +
      ']' + FCtMetaDatabase.Database;
    if PanelTbs.Visible then
      RefreshTbList(False, True)
    else if PanelTbs.Tag = 1 then
    begin
      PanelTbs.Tag := 0;
      sbtnToggleTbListClick(nil);
    end;
  end
  else
    lbStatus.Caption := 'Not connected';
end;

procedure TfrmDmlSqlEditor.actEditSelallExecute(Sender: TObject);
begin
  if ActiveControl is TCustomEdit then
    (ActiveControl as TCustomEdit).SelectAll
  else if ActiveControl is TComboBox then
    (ActiveControl as TComboBox).SelectAll;
end;

procedure TfrmDmlSqlEditor.actExecExecute(Sender: TObject);
begin
  DoExecute;
end;

procedure TfrmDmlSqlEditor.DoExecute;
  function IsSqlCmd(S: string): Boolean;
  var
    L: Integer;
  begin
    Result := False;
    if Pos('select', LowerCase(S))=0 then
      Result := True
    else if Pos('/*SQL_CMD*/', LowerCase(S))>0 then
      Result := True
    else
    begin
      S:=Trim(S);
      L:=Length(S);
      if L>0 then
        if S[L]=';' then
        begin
          Result := True;
        end;
    end;
  end;
var
  I: integer;
  Tm: int64;
  cr: TCursor;
  S, vT, exMsg: string;
  bSel: boolean;
begin             
  ValueListEditorDS.Clear;
  if Assigned(FBeforeExec) then
    FBeforeExec(Self);

  if (FCtMetaDatabase = nil) or not FCtMetaDatabase.Connected then
    ToolButton_ConnectClick(nil);
  if (FCtMetaDatabase = nil) or not FCtMetaDatabase.Connected then
    Exit;
  CheckLoadOldSql;

  SplitterFV.Visible := False;
  MemoFieldContent.DataField := '';

  S := MemoSql.SelText;
  bSel := Length(Trim(S)) >= 2;
  if not bSel then
    S := MemoSql.Lines.Text;
  //检测分号
  vT := SReplaceCRX(S);
  I := FOldSqlList.IndexOf(vT);
  if I = -1 then
  begin
    if (AutoExecSql = '') or (MemoSql.Lines.Text <> AutoExecSql) then
      FOldSqlList.Insert(0, vT);
  end
  else
  begin
    FOldSqlList.Move(I, 0);
  end;
  FNeedSaveIni := True;
  FExecSqlModified := False;

  DataSourceRes.DataSet := nil;
  if ResultDataSet <> nil then
  begin
    ResultDataSet.Close;
    FreeAndNil(ResultDataSet);
  end;
  //ResultDataSet.SQL.Text := S;

  //执行查询
  FRecCount := -1;
  lbStatus.Caption := srSqlEditorRunning;
  Panel2.Refresh;
  cr := Screen.Cursor;
  try
    Screen.Cursor := crAppStart;

    Tm := GetTickCount64;
    try              
      FCtMetaDatabase.ExecCmd('commit','','');
      if IsSqlCmd(S) or ((GetKeyState(VK_CONTROL) and $80) <> 0) then
        FCtMetaDatabase.ExecSql(S)
      else
      begin
        ResultDataSet := FCtMetaDatabase.OpenTable(S, '[ISSQL]');
        if ResultDataSet <> nil then
        begin
          DataSourceRes.DataSet := ResultDataSet;
          with DataSourceRes.DataSet.Fields do
            for I := 0 to Count - 1 do
              if Fields[I].DisplayWidth > 18 then
                Fields[I].DisplayWidth := 18;
        end;
      end;
    except
      on e: Exception do
      begin
        lbStatus.Caption := srSqlEditorRunError + e.Message;
        exMsg := '';
        if (e.Message=SErrNoSelectStatement) then
          exMsg := #13#10+srSqlEditorExecTip;
        Application.MessageBox(PChar(srSqlEditorRunError + e.Message + exMsg),
          PChar(Application.Title), MB_OK or MB_ICONERROR);
        Exit;
      end;
    end;


    Tm := int64(GetTickCount64) - Tm;
    lbStatus.Caption := Format(srSqlEditorRunFinishedFmt, [Tm / 1000]);
            
    if Assigned(FAfterExec) then
      FAfterExec(Self);
    ResultDataSet.AfterScroll:= _OnDSAfterScroll;
    if ValueListEditorDS.Visible then
      GenDSValList;
  finally
    Screen.Cursor := cr;
  end;
end;

procedure TfrmDmlSqlEditor.FormDestroy(Sender: TObject);
begin
  try
    SaveIniFile;
  except
  end;
  if FDmlSqlEditorForm = Self then
    FDmlSqlEditorForm := nil;

  if Assigned(FOldSqlList) then
    FreeAndNil(FOldSqlList);
end;

procedure TfrmDmlSqlEditor.ClearSql;
begin
  if ResultDataSet <> nil then
    if ResultDataSet.Active then
      ResultDataSet.Close;
  MemoSql.Clear;
  AutoExecSql := '';
  FHisIndex := -1;
end;

procedure TfrmDmlSqlEditor.PopupMenuOldSqlPopup(Sender: TObject);
begin
  CreateSqlMenus(PopupMenuOldSql.Items);
end;

procedure TfrmDmlSqlEditor.CreateSqlMenus(MMenuItem: TMenuItem);
var
  I, L: integer;
  MItem: TMenuItem;
begin
  if not Assigned(MMenuItem) then
    Exit;
  with MMenuItem do
  begin
    Clear;
    CheckLoadOldSql;
    if not Assigned(FOldSqlList) then
      Exit;

    for I := 0 to FOldSqlList.Count - 1 do
    begin
      L := Length(FOldSqlList.Strings[I]);
      if L = 0 then
        Continue;
      MItem := TMenuItem.Create(PopupMenuOldSql);
      if L <= 64 then
        MItem.Caption := FOldSqlList.Strings[I]
      else
        MItem.Caption := Copy(FOldSqlList.Strings[I], 1, 64) + '...';
      MItem.Tag := I;
      MItem.OnClick := MItem_SqlClick;
      Add(MItem);
    end;
  end;
end;

procedure TfrmDmlSqlEditor.MItem_SqlClick(Sender: TObject);
var
  MItem: TMenuItem;
begin
  if not Assigned(FOldSqlList) then
    Exit;
  if Sender is TMenuItem then
  begin
    MItem := TMenuItem(Sender);
    if (MItem.Tag >= 0) and (MItem.Tag < FOldSqlList.Count) then
      SetHistorySql(MItem.Tag);
  end;
end;

procedure TfrmDmlSqlEditor.SetHistorySql(Index: integer);
begin
  CheckLoadOldSql;
  if not Assigned(FOldSqlList) then
    Exit;
  if (FHisIndex = -1) and FExecSqlModified then
  begin
    FSaveCurSql := MemoSql.Lines.Text;
  end;
  if (Index >= 0) and (Index < FOldSqlList.Count) then
  begin
    MemoSql.Lines.Text := SReplaceCR(FOldSqlList.Strings[Index]);
    FExecSqlModified := False;
    FHisIndex := Index;
  end
  else
  begin
    MemoSql.Lines.Text := FSaveCurSql;
    FExecSqlModified := False;
    FHisIndex := -1;
  end;
end;

procedure TfrmDmlSqlEditor.MN_QueryRelTb_Click(Sender: TObject);
var
  S: String;
  frm: TfrmDmlSqlEditor;
begin
  S := TMenuItem(Sender).Hint;
  if Trim(S)='' then
    Exit;
  S := 'select * from '+S;
  frm := TfrmDmlSqlEditor.Create(Application);
  try
    frm.AutoExecSql := S;
    frm.MemoSql.Lines.Text := S;  
    frm.Position:=poDefault;
    frm.FormStyle:=fsStayOnTop;
    frm.Show;
  finally
    //frm.Release;
  end;
end;


procedure TfrmDmlSqlEditor.CheckLoadOldSql;
begin
  if not Assigned(FOldSqlList) then
  begin
    FOldSqlList := TStringList.Create;
    LoadIniFile;
  end;
end;

procedure TfrmDmlSqlEditor.actCopyRecExecute(Sender: TObject);
var
  S, V: string;
  I, L: integer;
begin
  if ResultDataSet <> nil then
    if ResultDataSet.Active then
      with ResultDataSet do
      begin
        L := 4;
        for I := 0 to FieldCount - 1 do
        begin
          if Length(Fields[I].FieldName) > L then
            L := Length(Fields[I].FieldName);
        end;
        S := '';
        for I := 0 to FieldCount - 1 do
        begin
          V := ExtStr(Fields[I].FieldName + ':', L + 3) + Fields[I].AsString;
          if I = 0 then
            S := V
          else
            S := S + #13#10 + V;
        end;
        if S <> '' then
          Clipboard.AsText := S;
      end;
end;

procedure TfrmDmlSqlEditor.DBGridResMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: integer);
var
  P: TPoint;
begin
  if (Button = mbRight) and not DBGridRes.EditorMode then
  begin
    P := DBGridRes.ClientToScreen(Point(X, Y));
    PopupMenuGrid.Popup(P.X, P.Y);
  end;
end;

procedure TfrmDmlSqlEditor.actCopyAllExecute(Sender: TObject);
var
  S, V, Ts: string;
  I: integer;
begin
  if ResultDataSet <> nil then
    if ResultDataSet.Active then
      with ResultDataSet do
      begin
        Ts := '';
        S := '';
        for I := 0 to FieldCount - 1 do
        begin
          if I = 0 then
            S := Fields[I].FieldName
          else
            S := S + #9 + Fields[I].FieldName;
        end;
        if Ts <> '' then
          Ts := Ts + #13#10;
        Ts := Ts + S;

        First;
        while not EOF do
        begin
          S := '';
          for I := 0 to FieldCount - 1 do
          begin
            if Fields[I].IsBlob then
              V := '(BLOB)'
            else
              V := Fields[I].AsString;
            if I = 0 then
              S := V
            else
              S := S + #9 + V;
          end;
          if Ts <> '' then
            Ts := Ts + #13#10;
          Ts := Ts + S;
          Next;
        end;
        if Ts <> '' then
          Clipboard.AsText := Ts;
      end;
end;

procedure TfrmDmlSqlEditor.LoadIniFile;
var
  AFileName, str: string;
  IniFile: TIniFile;
  I, C: integer;
begin
  AFileName := GetConfFileOfApp('') + '_SQL.INI';
  if not FileExists(AFileName) then
    Exit;

  IniFile := TIniFile.Create(AFileName);
  with IniFile do
    try
      C := ReadInteger('SqlQuery', 'HistorySqlCount', 0);
      if C <= 0 then
        Exit;
      FOldSqlList.Clear;

      for I := 0 to C - 1 do
      begin
        str := ReadString('SqlQuery', 'HistorySql' + IntToStr(I), '');
        if str <> '' then
        begin
          FOldSqlList.Add(str);
        end;
      end;

    finally
      Free;
    end;
end;

procedure TfrmDmlSqlEditor.SaveIniFile;
var
  AFileName: string;
  IniFile: TIniFile;
  I, C: integer;
begin
  if not Assigned(FOldSqlList) then
    Exit;
  AFileName := GetConfFileOfApp('') + '_SQL.INI';

  IniFile := TIniFile.Create(AFileName);
  with IniFile do
    try
      C := FOldSqlList.Count;
      if C <= 0 then
        Exit;
      if C > 20 then
        C := 20;
      WriteInteger('SqlQuery', 'HistorySqlCount', C);

      for I := 0 to C - 1 do
      begin
        WriteString('SqlQuery', 'HistorySql' + IntToStr(I), FOldSqlList[I]);
      end;

    finally
      Free;
    end;
end;

procedure TfrmDmlSqlEditor.PopupMenuGridPopup(Sender: TObject);
var
  mnQRelTable: TMenuItem;

  function GetFieldVal(fdn: string): string;    
  var
    V, eg: string;
    fd: TField;
  begin
    Result := fdn;  
    if ResultDataSet = nil then
      Exit;
    if not ResultDataSet.Active then
      Exit;
    fd := ResultDataSet.FindField(fdn);
    if fd = nil then
      Exit;
    V:=fd.AsString;
    eg := '';
    if Assigned(FCtMetaDatabase) then
      eg := FCtMetaDatabase.EngineType;
    case fd.DataType of
      ftDate,  ftTime, ftDateTime, ftTimeStamp:
      begin
        V := DBSqlStringToDateTime(V, eg);
      end;
      ftSmallint, ftInteger, ftWord, ftFloat, ftCurrency, ftBCD,
      ftAutoInc, ftLargeint, ftFMTBcd:
      begin
      end;
    else
      V := GetDbQuotString(V, eg);
    end;              
    if Trim(V) = '' then
      V := 'null';
    Result := V;
  end;
  procedure AddRelTb(tbMc, fdM, tbSc, fdS: String; bQueryMaster: Boolean);
  var
    S, V, tbM, tbS: string;
    mn: TMenuItem;
    po: Integer;
  begin
    tbM := tbMc;        
    po := Pos('(', tbM);
    if po>0 then
      tbM := Copy(tbM, 1, po-1);

    tbS := tbSc;
    po := Pos('(', tbS);
    if po>0 then
      tbS := Copy(tbS, 1, po-1);

    if tbM = '' then
      Exit; 
    if fdM = '' then
      Exit;
    if not bQueryMaster then
    begin
      S := tbSc + ': where '+ fdS +' = '+ GetFieldVal(fdM);
      V := tbS + ' where '+ fdS +' = '+ GetFieldVal(fdM);
    end
    else if tbS <> '' then
    begin
      S := tbMc + ': where '+ fdM +' = '+ GetFieldVal(fdS);
      V := tbM + ' where '+ fdM +' = '+ GetFieldVal(fdS);
    end
    else
    begin
      S := tbMc+': where '+ fdM +' = ' + GetFieldVal(fdM);
      V := tbM+' where '+ fdM +' = ' + GetFieldVal(fdM);
    end;


    mn:= TMenuItem.Create(Self);
    mn.Caption := S;
    mn.Hint := V;
    mn.OnClick:=MN_QueryRelTb_Click;
    mnQRelTable.Add(mn);
  end;

  function RecreateRelTbMenus(tbn: string): Boolean;
  var                  
    md: TCtDataModelGraph;
    atb, tb, tb2: TCtMetaTable;
    fd: TCtMetaField;
    I, J, K: Integer;
    mn: TMenuItem;     
    ss: TStringList;
  begin
    Result := False;
    if tbn = '' then
      Exit;
    {if mnQRelTable.Hint = tbn then
      Exit; }
    mnQRelTable.Hint := tbn;
    for I:=mnQRelTable.Count - 1 downto 0 do
    begin
      mn := mnQRelTable.Items[I];
      mnQRelTable.Delete(I);
      mn.Free;
    end;

    atb := FGlobeDataModelList.GetTableOfName(tbn);
    if atb=nil then
      Exit;
         
    Result := True;
        
    ss:= TStringList.Create;
    try
      ss.Add(atb.Name);
      if DBGridRes.SelectedField = nil then
      begin
        if atb.GetPrimaryKeyField <> nil then
          AddRelTb(atb.NameCaption, atb.GetPrimaryKeyField.Name, '', '', True)
      end
      else
        AddRelTb(atb.NameCaption, DBGridRes.SelectedField.FieldName, '', '', True);
      for K:=0 to atb.MetaFields.Count -1 do
      begin
        fd := atb.MetaFields.Items[K];
        if not fd.IsPhysicalField then
          Continue;
        if (fd.KeyFieldType <> cfktId) and (fd.KeyFieldType <> cfktRid) then
          Continue;
        if (fd.RelateTable <> '') and (fd.GetRelateTableField<>nil) then
        begin
          tb2 := fd.GetRelateTableObj;
          if tb2=nil then
            Continue;
          if tb2.Name = atb.Name then
            Continue;
          if ss.IndexOf(tb2.Name) < 0 then
          begin         
            ss.Add(tb2.Name);
            AddRelTb(tb2.NameCaption, fd.RelateField, atb.NameCaption, fd.Name, True);
          end;
        end;
      end;

      for I:=0 to FGlobeDataModelList.Count-1 do
      begin
        md := FGlobeDataModelList.Items[I];
        for J:=0 to md.Tables.Count-1 do
        begin
          tb := md.Tables.Items[J];
          if tb.Name = atb.Name then
            Continue;
          for K:=0 to tb.MetaFields.Count -1 do
          begin
            fd := tb.MetaFields.Items[K];
            if not fd.IsPhysicalField then
              Continue;
            if (fd.KeyFieldType <> cfktId) and (fd.KeyFieldType <> cfktRid) then
              Continue;
            if (UpperCase(fd.RelateTable)=UpperCase(tbN)) and (fd.GetRelateTableField <> nil) then
              begin
                if ss.IndexOf(tb.Name) < 0 then
                begin
                  ss.Add(tb.Name);
                  AddRelTb(atb.NameCaption, fd.RelateField, tb.NameCaption, fd.Name, False);
                end;
              end;
          end;
        end;
      end;
    finally
      ss.Free;
    end;
  end;
  function UnquotStr(S: string): string;
  var
    L: Integer;
  begin
    Result := Trim(S);
    L := Length(Result);
    if L > 2 then
    begin
      if (Result[1]='"') and (Result[1]=Result[L]) then
        Result := Copy(Result, 2, L-2)
      else if (Result[1]='[') and (Result[1]=Result[L]) then
        Result := Copy(Result, 2, L-2)
      else if (Result[1]='`') and (Result[1]=Result[L]) then
        Result := Copy(Result, 2, L-2);
    end;
  end;
var
  bEnb, bTbRels: Boolean;
  S: String;
begin
  if Sender = PopupMenuProp then
    mnQRelTable := MN_QueryRelTable2
  else
    mnQRelTable := MN_QueryRelTable;
  bEnb := Assigned(ResultDataSet) and ResultDataSet.Active;
  actCopyRec.Enabled := bEnb;
  actCopyCol.Enabled := bEnb;
  actCopyAll.Enabled := bEnb;      
  actChangeValue.Enabled := bEnb;
  actCopyFieldEqVal.Enabled := bEnb;

  S:='';
  if bEnb then
  begin
    S := TDataSetXX(ResultDataSet).PSGetTableName;
    if S <> '' then
      S := UnquotStr(S);
  end;

  bTbRels := RecreateRelTbMenus(S);
  mnQRelTable.Enabled := bTbRels;
  actShowSqlTbProp.Enabled := (S <> '');
  actShowSqlTbProp.Hint := S;
end;


procedure TfrmDmlSqlEditor.ToolButton_SQLMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  P: TPoint;
begin
  P := ToolButton_SQL.ClientToScreen(Point(0, ToolButton_SQL.Height + 1));
  PopupMenuOldSql.Popup(P.X, P.Y);
end;

procedure TfrmDmlSqlEditor.ToolButton_SqlNextClick(Sender: TObject);
begin
  if FHisIndex <> -1 then
    SetHistorySql(FHisIndex - 1)
  else
  begin
    MemoSql.Lines.Text := '';
    FExecSqlModified := False;
  end;
end;

procedure TfrmDmlSqlEditor.ToolButton_SqlPrevClick(Sender: TObject);
begin
  CheckLoadOldSql;
  if (FHisIndex = -1) or not Assigned(FOldSqlList) then
    SetHistorySql(0)
  else if FHisIndex < FOldSqlList.Count - 1 then
    SetHistorySql(FHisIndex + 1);
end;

procedure TfrmDmlSqlEditor.ValueListEditorDSEditingDone(Sender: TObject);
var
  S, V: String;
  fd: TField;
  aRow: Integer;
begin
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;
  if ResultDataSet.EOF then
    Exit;
  aRow := ValueListEditorDS.Row;
  S := ValueListEditorDS.Cells[0, aRow];
  if S='' then
    Exit;
  V := ValueListEditorDS.Cells[1, aRow];
  fd := ResultDataSet.FindField(S);
  if fd=nil then
    Exit;
  if fd.AsString = V then
    Exit;
  ResultDataSet.Edit;
  fd.AsString:=V;
end;

procedure TfrmDmlSqlEditor.ValueListEditorDSSelectCell(Sender: TObject; aCol,
  aRow: Integer; var CanSelect: Boolean);
var
  S: String;
  fd: TField;
begin                          
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;
  S := ValueListEditorDS.Cells[0, aRow];
  if S='' then
    Exit;
  fd := ResultDataSet.FindField(S);
  if fd=nil then
    Exit;
  DBGridRes.SelectedField := fd;
end;

procedure TfrmDmlSqlEditor.SetReadOnlyMode(const Value: boolean);
var
  cl: TColor;
begin
  FReadOnlyMode := Value;
  MemoSql.ReadOnly := FReadOnlyMode;
  if FReadOnlyMode then
    cl := clCream
  else
    cl := clWindow;
  MemoSql.Color := cl;
  DBGridRes.Color := cl;
  ToolButton_ClearAll.Enabled := not FReadOnlyMode;
end;

procedure TfrmDmlSqlEditor.SetSplitPercent(const Value: double);
begin
  FSplitPercent := Value;
  FormResize(nil);
end;

procedure TfrmDmlSqlEditor.RefreshTbList(bForce, bCheckMode: boolean);
var
  S, dbus: string;
  I: integer;
begin
  if not Assigned(FCtMetaDatabase) or not FCtMetaDatabase.Connected then
    Exit;
  S := '[' + FCtMetaDatabase.EngineType + ']' + FCtMetaDatabase.Database;
  if (FLastTbListDBInfo = S) and not bForce and bCheckMode then
    Exit;
  FLastTbListDBInfo := S;

  Screen.Cursor := crAppStart;
  try
    if bForce or (combDbUsers.Items.Count = 0) then
    begin
      combDbUsers.Items.Text := FCtMetaDatabase.GetDbUsers;
      if G_LastMetaDbSchema <> '' then
      begin  
        dbus := G_LastMetaDbSchema;
        for I := 0 to combDbUsers.Items.Count - 1 do
          if UpperCase(combDbUsers.Items[I]) = UpperCase(dbus) then
            combDbUsers.ItemIndex := I;
      end;
      if combDbUsers.Text = '' then
      begin
        dbus := FCtMetaDatabase.DbSchema;
        if dbus = '' then
          dbus := FCtMetaDatabase.User;
        for I := 0 to combDbUsers.Items.Count - 1 do
          if UpperCase(combDbUsers.Items[I]) = UpperCase(dbus) then
            combDbUsers.ItemIndex := I;
      end;
    end;
    ListBoxTbs.Items.Assign(FCtMetaDatabase.GetDbObjs(combDbUsers.Text));
    FLastTbListStr := '';
    edtTbListFilter.Text := '';  
    FLastTbListStr := ListBoxTbs.Items.Text;
  finally
    Screen.Cursor := crDefault;
  end;
  if combDbUsers.Text <> '' then
    G_LastMetaDbSchema := combDbUsers.Text;
end;

procedure TfrmDmlSqlEditor.DoDbTableAction(act: string);
var
  o: TCtMetaObject;
  I, po: integer;
  db, obj: string;
begin
  if FCtMetaDatabase = nil then
    Exit;
  I := ListBoxTbs.ItemIndex;
  if I < 0 then
    Exit;
  obj := ListBoxTbs.Items[I];
  db := combDBUsers.Text;

  if db = '' then
  begin
    po := Pos('.', obj);
    if po > 0 then
    begin
      db := Copy(obj, 1, po - 1);
      obj := Copy(obj, po + 1, Length(obj));
    end;
  end;

  Screen.Cursor := crAppStart;
  try
    o := FCtMetaDatabase.GetObjInfos(db, obj, '');
  finally
    Screen.Cursor := crDefault;
  end;

  if o = nil then
    raise Exception.Create(srDmlSearchNotFound + ' - ' + db+IfElse(db<>'','.','')+obj);
  try
    if act = 'show_props' then
    begin
      if not Assigned(Proc_ShowCtTableOfField) then
        raise Exception.Create('Proc_ShowCtTableOfField not assigned');
      Proc_ShowCtTableOfField(TCtMetaTable(o), nil, True, False, True);
    end;

    if act = 'get_select_sql' then
    begin
      MemoSql.Lines.Text := TCtMetaTable(o).GenSelectSql(G_MaxRowCountForTableData,
        FCtMetaDatabase.EngineType, FCtMetaDatabase);
      actExec.Execute;
    end;
  finally
    o.Free;
  end;
end;

function TfrmDmlSqlEditor.GetCurRecInserSql(tbn, dbType: string): string;
var
  sFields, sVals, S, V: string;
  I: integer;
  fd: TField;
begin
  Result := '';
  if ResultDataSet = nil then
    Exit;
  if not ResultDataSet.Active then
    Exit;

  sFields := '';
  sVals := '';
  with ResultDataSet do
  begin
    for I := 0 to FieldCount - 1 do
    begin
      fd := Fields[I];
      if UpperCase(fd.FieldName)='ROWID' then
        Continue;
      if UpperCase(fd.FieldName)='_ROWID' then
        Continue;
      if dbType='POSTGRESQL' then
        if UpperCase(fd.FieldName)='CTID' then
          Continue;
      V := '  '+fd.FieldName;
      if I = 0 then
        sFields := V
      else
        sFields := sFields + ','#13#10 + V;

      if fd.IsNull then
        V:='null'
      else
      case fd.DataType of
        ftSmallint, ftInteger, ftWord,
        ftFloat, ftCurrency, ftBCD, ftAutoInc, ftFMTBcd, ftLargeint:
          V := fd.AsString;
        ftBoolean:
          V := IntToStr(fd.AsInteger);
        ftDate,  ftTime, ftDateTime,ftTimeStamp:
        begin
          if fd.AsDateTime=0 then
            V := 'null'
          else
          begin
            if Trunc(fd.AsDateTime)=fd.AsDateTime then
              V := FormatDateTime('yyyy-mm-dd', fd.AsDateTime)
            else
              V := FormatDateTime('yyyy-mm-dd hh:nn:ss', fd.AsDateTime);
            V := DBSqlStringToDateTime(V, dbType);
          end;
        end;
        ftString, ftMemo,  ftFmtMemo, ftFixedChar, ftWideString,
        ftOraClob, ftGuid, ftFixedWideChar, ftWideMemo:
        begin
          V := fd.AsString;
          if V = '' then
            V := 'null'
          else
          begin
            V := GetDbQuotString(V, dbType);
          end;
        end
      else
        //ftBytes, ftVarBytes,  ftBlob,   ftGraphic,
        //ftParadoxOle, ftDBaseOle, ftTypedBinary, ftCursor,   ftADT, ftArray, ftReference,
        //ftDataSet, ftOraBlob,  ftVariant, ftInterface,
        //ftIDispatch,
        V:='null/*(unsupported_value_type)*/';
      end;

      if V = '' then
        V := 'null';
      V := '  '+V;
      if I = 0 then
        sVals := V
      else
        sVals := sVals + ','#13#10 + V;
    end;
  end;

  Result := 'insert into '+tbn+'('#13#10+sFields+#13#10') values ('#13#10+sVals+#13#10');';
end;

procedure TfrmDmlSqlEditor.GenDSValList;
var
  I: integer;
begin
  if ResultDataSet <> nil then
    if ResultDataSet.Active then
      with ResultDataSet do
      begin
        for I := 0 to FieldCount - 1 do
        begin
          ValueListEditorDS.Values[Fields[I].FieldName] := Fields[I].AsString;
        end;
      end;
end;

procedure TfrmDmlSqlEditor._OnDSAfterScroll(DataSet: TDataSet);
begin
  if ValueListEditorDS.Visible then
    GenDSValList;
end;

procedure TfrmDmlSqlEditor.actExecUpdate(Sender: TObject);
begin
  actExec.Enabled := MemoSql.CanFocus;
end;

end.
